package cn.anecansaitin.whimsy.common.entity;

import cn.anecansaitin.whimsy.common.register.ModEntities;
import net.minecraft.core.BlockPos;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.projectile.ProjectileUtil;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.TheEndGatewayBlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.EntityHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import software.bernie.geckolib.animatable.GeoEntity;
import software.bernie.geckolib.constant.DefaultAnimations;
import software.bernie.geckolib.core.animatable.GeoAnimatable;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
import software.bernie.geckolib.core.animation.AnimationController;
import software.bernie.geckolib.core.animation.RawAnimation;
import software.bernie.geckolib.util.GeckoLibUtil;

public class DioriteMissile extends Projectile implements GeoEntity {
    private static final EntityDataAccessor<Byte> DATA_SCALE = SynchedEntityData.defineId(DioriteMissile.class, EntityDataSerializers.BYTE);
    private int remainingTick = 20 * 10;
    private final AnimatableInstanceCache geoCache = GeckoLibUtil.createInstanceCache(this);
    private static final RawAnimation hide1 = RawAnimation.begin()
            .thenPlay("parts.hide1");
    private static final RawAnimation hide2 = RawAnimation.begin()
            .thenPlay("parts.hide2");
    private static final RawAnimation hide3 = RawAnimation.begin()
            .thenPlay("parts.hide3");
    private static final RawAnimation hide4 = RawAnimation.begin()
            .thenPlay("parts.hide4");
    private static final RawAnimation show1 = RawAnimation.begin()
            .thenPlay("parts.show1");
    private static final RawAnimation show2 = RawAnimation.begin()
            .thenPlay("parts.show2");
    private static final RawAnimation show3 = RawAnimation.begin()
            .thenPlay("parts.show3");
    private static final RawAnimation show4 = RawAnimation.begin()
            .thenPlay("parts.show4");

    public DioriteMissile(EntityType<? extends Projectile> entityType, Level level) {
        super(entityType, level);
        setNoGravity(true);
    }

    public DioriteMissile(Level level, LivingEntity shooter, byte scale) {
        this(ModEntities.DIORITE_MISSILE.get(), level);
        float x = shooter.getXRot();
        float y = shooter.getYRot();
        float f = -Mth.sin(y * ((float) Math.PI / 180F)) * Mth.cos(x * ((float) Math.PI / 180F));
        float f1 = -Mth.sin((x + 0) * ((float) Math.PI / 180F));
        float f2 = Mth.cos(y * ((float) Math.PI / 180F)) * Mth.cos(x * ((float) Math.PI / 180F));
        setPos(shooter.getX() + f / 2, shooter.getEyeY() - 0.1 + f1 / 2, shooter.getZ() + f2 / 2);
        setOwner(shooter);
        setScale(scale);
    }

    @Override
    public void tick() {
        if (remainingTick-- <= 0) {
            discard();
            return;
        }

        super.tick();

        HitResult hitresult = ProjectileUtil.getHitResultOnMoveVector(this, this::canHitEntity);
        boolean flag = false;
        if (hitresult.getType() == HitResult.Type.BLOCK) {
            BlockPos blockpos = ((BlockHitResult) hitresult).getBlockPos();
            BlockState blockstate = this.level().getBlockState(blockpos);
            if (blockstate.is(Blocks.NETHER_PORTAL)) {
                this.handleInsidePortal(blockpos);
                flag = true;
            } else if (blockstate.is(Blocks.END_GATEWAY)) {
                BlockEntity blockentity = this.level().getBlockEntity(blockpos);
                if (blockentity instanceof TheEndGatewayBlockEntity && TheEndGatewayBlockEntity.canEntityTeleport(this)) {
                    TheEndGatewayBlockEntity.teleportEntity(this.level(), blockpos, blockstate, this, (TheEndGatewayBlockEntity) blockentity);
                }

                flag = true;
            }
        }

        if (hitresult.getType() != HitResult.Type.MISS && !flag && !net.neoforged.neoforge.event.EventHooks.onProjectileImpact(this, hitresult)) {
            this.onHit(hitresult);
        }

        this.checkInsideBlocks();
        Vec3 vec3 = this.getDeltaMovement();
        double d2 = this.getX() + vec3.x;
        double d0 = this.getY() + vec3.y;
        double d1 = this.getZ() + vec3.z;
        this.updateRotation();

        if (this.isInWater()) {
            for (int i = 0; i < 4; ++i) {
                this.level().addParticle(ParticleTypes.BUBBLE, d2 - vec3.x * 0.25D, d0 - vec3.y * 0.25D, d1 - vec3.z * 0.25D, vec3.x, vec3.y, vec3.z);
            }
        }

        level().addParticle(ParticleTypes.ENCHANT, position().x, position().y, position().z, 0, 0, 0);

        this.setPos(d2, d0, d1);
    }

    @Override
    protected void onHit(HitResult pResult) {
        super.onHit(pResult);
        discard();
    }

    @Override
    protected void onHitEntity(EntityHitResult result) {
        byte scale = getScale();

        if (scale > 0) {
            result.getEntity().hurt(damageSources().indirectMagic(this, getOwner()), ((scale - 1) * 0.1f + 1) * 20);
        } else {
            result.getEntity().hurt(damageSources().indirectMagic(this, getOwner()), 8);
        }
        //todo 添加一种当此射击内的所有伤害不被忽略的伤害类型
    }

    private byte getScale() {
        return getEntityData().get(DATA_SCALE);
    }

    private void setScale(byte scale) {
        getEntityData().set(DATA_SCALE, scale);
    }

    @Override
    public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
        controllers.add(new AnimationController<GeoAnimatable>(this, "con", 0, (e) -> e.setAndContinue(DefaultAnimations.IDLE)));
        controllers.add(new AnimationController<GeoAnimatable>(this, "part1", 0, (e) -> getScale() > 1 ? e.setAndContinue(show1) : e.setAndContinue(hide1)));
        controllers.add(new AnimationController<GeoAnimatable>(this, "part2", 0, (e) -> getScale() > 2 ? e.setAndContinue(show2) : e.setAndContinue(hide2)));
        controllers.add(new AnimationController<GeoAnimatable>(this, "part3", 0, (e) -> getScale() > 3 ? e.setAndContinue(show3) : e.setAndContinue(hide3)));
        controllers.add(new AnimationController<GeoAnimatable>(this, "part4", 0, (e) -> getScale() > 4 ? e.setAndContinue(show4) : e.setAndContinue(hide4)));
    }

    @Override
    public AnimatableInstanceCache getAnimatableInstanceCache() {
        return geoCache;
    }

    @Override
    protected void defineSynchedData() {
        getEntityData().define(DATA_SCALE, (byte) 0);
    }

    @Override
    protected void addAdditionalSaveData(CompoundTag compound) {
        super.addAdditionalSaveData(compound);
        compound.putByte("scale", getScale());
    }

    @Override
    protected void readAdditionalSaveData(CompoundTag compound) {
        super.readAdditionalSaveData(compound);
        setScale(compound.getByte("scale"));
    }

    @Override
    public void shootFromRotation(Entity pShooter, float pX, float pY, float pZ, float pVelocity, float pInaccuracy) {
        float f = -Mth.sin(pY * ((float)Math.PI / 180F)) * Mth.cos(pX * ((float)Math.PI / 180F));
        float f1 = -Mth.sin((pX + pZ) * ((float)Math.PI / 180F));
        float f2 = Mth.cos(pY * ((float)Math.PI / 180F)) * Mth.cos(pX * ((float)Math.PI / 180F));
        this.shoot(f, f1, f2, pVelocity, pInaccuracy);
        Vec3 vec3 = pShooter.getDeltaMovement();
        this.setDeltaMovement(this.getDeltaMovement().add(vec3.x, 0.0D, vec3.z));
    }
}
